iT邦幫忙

2025 iThome 鐵人賽

DAY 5
0
Rust

Rust 實戰專案集:30 個漸進式專案從工具到服務系列 第 5

密碼產生器 - 可自訂規則的安全密碼產生工具

  • 分享至 

  • xImage
  •  

前言

今天我們的主題是安全密碼產生工具,在目前想到的規劃中,
有些小工具如果使用,那我會覺得挺好用的,以密碼產生工具來說那會是值得選擇的作法

專案目標

  • 支援多種字符集(大小寫字母、數字、特殊符號)
  • 可自訂密碼長度和複雜度規則
  • 支援批量產生密碼
  • 提供密碼強度評估
  • 命令行介面操作

補充

現今其實有很多方式產生安全性密碼的做法,像是我個人如果如果只想用簡單的,
那我個人會用 openssl rand -hex 16 或是 base64 去產生一段密碼使用,
不但如此,現在瀏覽器,或是 mac 都有內建可以產生密碼的好東西。
不過當然,能自己做一個再好不過了

依賴

說到密碼,肯定有一定的亂數層面

rand : 我們用這個產生隨機文字

clap : 我們用這個處理 cli 參數

開始實作

cargo new password_generator
cd password_generator

cargo.toml

[dependencies]
rand = "0.8"
clap = { version = "4.0", features = ["derive"] }

定義密碼規則資料結構

use clap::Parser;
use rand::{thread_rng, Rng};
use std::collections::HashSet;

#[derive(Parser)]
#[command(name = "password_generator")]
#[command(about = "安全密碼產生器")]
pub struct Args {
    /// 密碼長度
    #[arg(short, long, default_value_t = 12)]
    pub length: usize,
    
    /// 包含大寫字母
    #[arg(long, default_value_t = true)]
    pub uppercase: bool,
    
    /// 包含小寫字母
    #[arg(long, default_value_t = true)]
    pub lowercase: bool,
    
    /// 包含數字
    #[arg(long, default_value_t = true)]
    pub numbers: bool,
    
    /// 包含特殊符號
    #[arg(long, default_value_t = false)]
    pub symbols: bool,
    
    /// 排除相似字符 (0, O, l, 1, etc.)
    #[arg(long, default_value_t = false)]
    pub exclude_ambiguous: bool,
    
    /// 產生密碼數量
    #[arg(short, long, default_value_t = 1)]
    pub count: usize,
    
    /// 每個字符集的最小數量
    #[arg(long, default_value_t = 1)]
    pub min_each: usize,
}

#[derive(Debug)]
pub struct PasswordConfig {
    pub length: usize,
    pub character_sets: Vec<&'static str>,
    pub min_each: usize,
    pub exclude_ambiguous: bool,
}

核心邏輯

pub struct PasswordGenerator {
    config: PasswordConfig,
}

impl PasswordGenerator {
    pub fn new(config: PasswordConfig) -> Result<Self, String> {
        if config.length < 4 {
            return Err("密碼長度至少需要 4 個字符".to_string());
        }
        
        if config.character_sets.is_empty() {
            return Err("至少需要選擇一種字符集".to_string());
        }
        
        let min_required = config.character_sets.len() * config.min_each;
        if config.length < min_required {
            return Err(format!(
                "密碼長度 {} 不足以滿足每種字符集最少 {} 個字符的要求", 
                config.length, config.min_each
            ));
        }
        
        Ok(Self { config })
    }
    
    pub fn generate(&self) -> String {
        let mut rng = thread_rng();
        let mut password = Vec::new();
        
        // 確保每種字符集都有最少數量的字符
        for charset in &self.config.character_sets {
            let filtered_charset = if self.config.exclude_ambiguous {
                self.filter_ambiguous_chars(charset)
            } else {
                charset.to_string()
            };
            
            for _ in 0..self.config.min_each {
                let chars: Vec<char> = filtered_charset.chars().collect();
                let random_char = chars[rng.gen_range(0..chars.len())];
                password.push(random_char);
            }
        }
        
        // 填充剩餘長度
        let all_chars = self.get_all_characters();
        let remaining = self.config.length - password.len();
        
        for _ in 0..remaining {
            let chars: Vec<char> = all_chars.chars().collect();
            let random_char = chars[rng.gen_range(0..chars.len())];
            password.push(random_char);
        }
        
        // 隨機打亂密碼
        for i in 0..password.len() {
            let j = rng.gen_range(0..password.len());
            password.swap(i, j);
        }
        
        password.into_iter().collect()
    }
    
    fn get_all_characters(&self) -> String {
        let mut all_chars = String::new();
        for charset in &self.config.character_sets {
            if self.config.exclude_ambiguous {
                all_chars.push_str(&self.filter_ambiguous_chars(charset));
            } else {
                all_chars.push_str(charset);
            }
        }
        all_chars
    }
    
    fn filter_ambiguous_chars(&self, charset: &str) -> String {
        let ambiguous_chars: HashSet<char> = 
            "0O1lI|`".chars().collect();
        
        charset.chars()
            .filter(|c| !ambiguous_chars.contains(c))
            .collect()
    }
}

這裡定義我們會用到的字符

pub const UPPERCASE: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
pub const LOWERCASE: &str = "abcdefghijklmnopqrstuvwxyz";
pub const NUMBERS: &str = "0123456789";
pub const SYMBOLS: &str = "!@#$%^&*()_+-=[]{}|;:,.<>?";

pub fn build_config_from_args(args: &Args) -> Result<PasswordConfig, String> {
    let mut character_sets = Vec::new();
    
    if args.uppercase {
        character_sets.push(UPPERCASE);
    }
    if args.lowercase {
        character_sets.push(LOWERCASE);
    }
    if args.numbers {
        character_sets.push(NUMBERS);
    }
    if args.symbols {
        character_sets.push(SYMBOLS);
    }
    
    PasswordConfig {
        length: args.length,
        character_sets,
        min_each: args.min_each,
        exclude_ambiguous: args.exclude_ambiguous,
    }
}

這裡我們來製作評估強度的部分

pub fn evaluate_password_strength(password: &str) -> (&'static str, f32) {
    let mut score = 0f32;
    let length = password.len() as f32;
    
    // 長度評分
    score += (length / 4.0).min(6.0);
    
    // 字符多樣性評分
    let has_lower = password.chars().any(|c| c.is_ascii_lowercase());
    let has_upper = password.chars().any(|c| c.is_ascii_uppercase());
    let has_digit = password.chars().any(|c| c.is_ascii_digit());
    let has_symbol = password.chars().any(|c| !c.is_alphanumeric());
    
    let variety = [has_lower, has_upper, has_digit, has_symbol]
        .iter().filter(|&&x| x).count() as f32;
    
    score += variety * 2.0;
    
    // 熵評估
    let unique_chars = password.chars().collect::<HashSet<_>>().len() as f32;
    score += (unique_chars / length * 4.0).min(4.0);
    
    let strength = match score as i32 {
        0..=6 => ("弱", score / 16.0),
        7..=10 => ("中等", score / 16.0),
        11..=14 => ("強", score / 16.0),
        _ => ("非常強", score / 16.0),
    };
    
    strength
}

主程式

use clap::Parser;

mod password_generator;
use password_generator::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args = Args::parse();
    
    // 驗證參數
    if args.length == 0 {
        eprintln!("錯誤: 密碼長度必須大於 0");
        std::process::exit(1);
    }
    
    if args.count == 0 {
        eprintln!("錯誤: 密碼數量必須大於 0");
        std::process::exit(1);
    }
    
    // 建立密碼配置
    let config = match build_config_from_args(&args) {
        Ok(config) => config,
        Err(e) => {
            eprintln!("配置錯誤: {}", e);
            std::process::exit(1);
        }
    };
    
    // 建立密碼產生器
    let generator = match PasswordGenerator::new(config) {
        Ok(gen) => gen,
        Err(e) => {
            eprintln!("產生器初始化失敗: {}", e);
            std::process::exit(1);
        }
    };
    
    // 產生密碼
    println!("=== 安全密碼產生器 ===");
    println!("配置: 長度={}, 數量={}", args.length, args.count);
    println!("字符集: {}{}{}{}",
        if args.uppercase { "大寫 " } else { "" },
        if args.lowercase { "小寫 " } else { "" },
        if args.numbers { "數字 " } else { "" },
        if args.symbols { "符號 " } else { "" }
    );
    
    if args.exclude_ambiguous {
        println!("已排除相似字符");
    }
    
    println!("\n產生的密碼:");
    println!("{:-<50}", "");
    
    for i in 1..=args.count {
        let password = generator.generate();
        let (strength, score) = evaluate_password_strength(&password);
        
        println!("密碼 {:2}: {}", i, password);
        println!("       強度: {} ({:.1}%)", strength, score * 100.0);
        
        if i < args.count {
            println!();
        }
    }
    
    println!("{:-<50}", "");
    println!("提醒: 你的密碼已經完成了好誒~~");
    
    Ok(())
}

快速使用起來

# 產生預設 12 位密碼
cargo run

# 產生 16 位包含符號的密碼
cargo run -- --length 16 --symbols

# 產生 5 個 20 位的複雜密碼
cargo run -- --length 20 --symbols --count 5

# 產生不包含相似字符的密碼
cargo run -- --length 15 --symbols --exclude-ambiguous

# 只使用數字和小寫字母
cargo run -- --length 10 --no-uppercase --symbols

Day 5 :: 打完收工,不仿自己玩玩看~~


上一篇
目錄分析器 - 分析資料夾大小並產生報告
系列文
Rust 實戰專案集:30 個漸進式專案從工具到服務5
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言